import { trackTypes, triggerTypes } from "./effectType.js";
import targetMap from "./effectMap.js";
import scheduler from "./scheduler.js";
let shouldTrack = true;
const ITERATOR = Symbol("iterator");
// 暂停依赖收集
export function pauseTrack() {
  shouldTrack = false;
}
// 恢复依赖收集
export function resumeTrack() {
  shouldTrack = true;
}

// 每次运行时，应该清空对应依赖函数，重新构建该依赖函数的收集
// 这是因为副作用函数每次运行时的依赖可能时是不同，具体的依赖有哪些，需要在运行时才能确定，所以需要每次运行时，都需要重新构建“该副作用函数”与所“依赖数据”的关系
export function clearEffect(effectFn) {
  const deps = effectFn.deps;
  // console.log("clearEffect", deps);
  if (deps) {
    deps.forEach((dep) => {
      dep.delete(effectFn);
    });
    deps.length = 0;
  }
}

// 定义一个执行栈，用来存储当前正在执行的effect，方便track时，可以知道当前正在收集哪个effect的依赖
// 为什么要用执行栈的形式来存储呢，因为effect可以嵌套使用，如果直接使用变量，那么就有可能内部嵌套的effect收集完，直接清空了外部effect的依赖，导致外部effect无法收集到依赖
// 所以使用执行栈的形式来存储effectFn，这样，外部依赖不会丢失，且则栈顶的effectFn就是当前正在执行的effectFn
const effectStack = [];
// 定义一个变量，指向当前运行的effectFn
let activeEffectfn = null;
// 创建依赖收集函数运行环境
function createEffectFn(fn, options = {}) {
  function effectFn() {
    let result;
    try {
      // 运行fn前，将当前执行环境保存下来
      activeEffectfn = effectFn;
      effectStack.push(effectFn);
      // 运行fn前，清空当前副作用函数的依赖关系
      clearEffect(effectFn);
      // 运行时，重新构建副作用函数与依赖数据的关系
      result = fn();
    } finally {
      // 运行完fn后，将当前执行环境置空
      effectStack.pop();
      activeEffectfn = effectStack[effectStack.length - 1];
    }
    return result;
  }
  // 创建一个数组，用来存储当前effectFn的依赖集
  effectFn.deps = [];
  return effectFn;
}

// 标记依赖函数
export function effect(fn, options = {}) {
  // lazy：是否懒执行，默认为false，即默认立即执行
  // scheduler：调度器，当依赖数据发生变化时，会触发trigger，此时会执行scheduler，而不是直接执行fn
  const { lazy = false, scheduler = null } = options;
  const effectFn = createEffectFn(fn, options);
  if (scheduler) {
    effectFn.options = options;
  }
  if (lazy) {
    console.log("effectFn", effectFn.options);
    return effectFn;
  }
  effectFn();
}

// 依赖收集
export function track(target, type, key) {
  if (!shouldTrack) {
    return;
  }
  // console.log(`%c track 【${type}】`, "color: #f00", key || "iterator");
  console.log(`%c track 【${type}】`, "color: #f00", key || "iterator", target);
  // console.log("activeEffectfn", activeEffectfn);
  // 存储依赖结构 targetMap(对象) -> propMap(属性) -> typeMap(类型操作) -> deps(依赖集)
  let propMap = targetMap.get(target);
  if (!propMap) {
    // 如果当前对象没有收集过依赖，则创建一个Map来存储其属性依赖
    targetMap.set(target, new Map());
    propMap = targetMap.get(target);
  }
  // 对迭代读取数据的行为单独处理，因为迭代的行为，会触发get、has、iterator三种类型操作，所以需要单独处理
  // 如for...in、for...of、Object.keys()、Object.values()、Object.entries()等
  const KEY = type === trackTypes.ITERATE ? ITERATOR : key;
  let typeMap = propMap.get(KEY);
  if (!typeMap) {
    // 如果当前属性的类型操作没有收集过依赖，则创建一个Map按类型操作（get、has、iterator）来存储其依赖
    propMap.set(KEY, new Map());
    typeMap = propMap.get(KEY);
  }
  let deps = typeMap.get(type);
  if (!deps) {
    // 如果当前属性的类型操作没有收集过依赖，则创建一个Set来存储其依赖
    // set集合可以出重相同的副作用函数
    typeMap.set(type, new Set());
    deps = typeMap.get(type);
  }
  // console.log("activeEffectfn", activeEffectfn);
  // 如果是在外界直接读取属性，这个时候activeEffectfn就是undefined，所以需要判断一下
  if (activeEffectfn) {
    // 将当前正在执行的effectFn添加到依赖集中
    deps.add(activeEffectfn);
    // 将当前deps依赖集存储到当前effectFnMap映射上，方便后续clear时，可以快速找到对应的deps
    activeEffectfn.deps.push(deps);
    // console.log(key, activeEffectfn.deps);
    // console.log("targetMap", targetMap);
  }
}

// 建立行为动作与触发动作的关系，方便做区分
const triggerMaps = {
  [triggerTypes.SET]: [trackTypes.GET],
  [triggerTypes.ADD]: [trackTypes.GET, trackTypes.HAS, trackTypes.ITERATE],
  [triggerTypes.DELETE]: [trackTypes.GET, trackTypes.HAS, trackTypes.ITERATE],
};

// 派发更新
export function trigger(target, type, key, newValue, oldValue) {
  // console.log(`%c trigger【${type}】`, "color: #00f", key);
  console.log(`%c trigger【${type}】`, "color: #00f", key, target);
  const propMap = targetMap.get(target);
  if (!propMap) {
    return;
  }
  // console.log("propMap", propMap);
  const keys = [key];
  // 考虑迭代读取数据的情况，这种情况下，会触发get、has、iterator三种类型操作，所以需要单独处理
  // 那么新增或者删除属性，都会对get、has、iterator三种类型操作有影响，这个时候派发更新时，需要将get、has、iterator三种类型操作都派发出去
  if (type === triggerTypes.ADD || type === triggerTypes.DELETE) {
    keys.push(ITERATOR);
  }
  // 副作用函数收集过程也有可能重复，利用Set去重
  const deps = new Set();
  keys.forEach((key) => {
    const typeMap = propMap.get(key);
    if (typeMap) {
      triggerMaps[type].forEach((type) => {
        const dep = typeMap.get(type);
        if (dep) {
          deps.add(...dep);
        }
      });
    }
  });
  if (!deps) {
    return;
  }
  deps.forEach((effectFn) => {
    // 如果派发更新执行的函数与当前收集依赖的副作用函数时同一个，就会出现tarck和trigger相互调用的情况，导致栈溢出
    // 所以需要判断一下，如果派发更新执行的函数与当前收集依赖的副作用函数时同一个，则不执行派发更新
    if (effectFn !== activeEffectfn) {
      if (effectFn.options && effectFn.options.scheduler) {
        effectFn.options.scheduler({ effectFn, newValue, oldValue });
      } else {
        scheduler(effectFn, key);
      }
    }
  });
}
